package cn.zswltech.gruul.web.api.controller;

import cn.zswltech.gruul.api.LoginApi;
import cn.zswltech.gruul.auth.ThirdApplicationAuthService;
import cn.zswltech.gruul.common.entity.DisplayMenuTreeDO;
import cn.zswltech.gruul.common.entity.SaltDO;
import cn.zswltech.gruul.common.entity.SystemConfigDO;
import cn.zswltech.gruul.common.entity.UserDO;
import cn.zswltech.gruul.biz.service.CockpitMobileService;
import cn.zswltech.gruul.biz.service.LoginService;
import cn.zswltech.gruul.biz.service.SystemConfigService;
import cn.zswltech.gruul.biz.service.UserService;
import cn.zswltech.gruul.common.constant.SystemConfigConstant;
import cn.zswltech.gruul.common.constant.SystemConstant;
import cn.zswltech.gruul.common.result.MSG;
import cn.zswltech.gruul.common.result.Response;
import cn.zswltech.gruul.common.util.*;
import cn.zswltech.gruul.common.dal.dao.OaTicketAccountMapper;
import cn.zswltech.gruul.common.dal.dao.SaltDOMapper;
import cn.zswltech.gruul.common.dal.dao.UserDOMapper;
import cn.zswltech.gruul.common.dal.query.LoginQuery;
import cn.zswltech.gruul.domain.entity.CheckResult;
import cn.zswltech.gruul.web.api.util.*;
import cn.zswltech.gruul.web.api.util.RequestUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import tk.mybatis.mapper.entity.Example;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;


@RestController
@Slf4j
public class LoginController implements LoginApi {

    @Autowired
    private ThirdApplicationAuthService thirdApplicationAuthService;

    @Resource(name = "loginService")
    private LoginService loginService;

    @Resource(name = "userServiceAPI")
    private UserService userService;

    @Resource
    private UserDOMapper userDOMapper;

    @Resource
    private SaltDOMapper saltDOMapper;

    @Value("${system.token.expiration}")
    public long tokenExpiration;

    @Autowired
    private SystemConfigService systemConfigService;

    @Resource
    private CommonFunctionCheck commonFunctionCheck;

    @Resource
    private RestTemplate restTemplate;

    @Resource
    private CockpitMobileService cockpitMobileService;

    @Resource
    private OaTicketAccountMapper oaTicketAccountMapper;

    @Value("${cico.portal.istest}")
    private Boolean cicoPortalIsTest;
    @Value("${cico.portal.validateUrl}")
    private String cicoPortalValidateUrl;
    @Value("${cico.portal.service}")
    private String cicoPortalService;
    @Value("${cico.portal.targetPath}")
    private String cicoPortalTargetPath;
    @Value("${spring.profiles.active}")
    private String profilesActive;
    // 去集团sso验证ticket有效性并换取用户信息的请求地址
    @Value("${dashboard.sso.validateTicket.url}")
    private String dashboardSSOValidateTicketUrl;
    // 当前系统注册在sso中的地址，验证ticket有效性并换取用户信息时需要用到
    @Value("${dashboard.sso.service}")
    private String dashboardSSOService;
    @Value("${h5Cockpit.sso.isTest}")
    private Boolean h5CockpitSSOIsTest;
    @Value("${h5Cockpit.sso.desSecret}")
    private String h5CockpitSSODesSecret;
    @Value("${h5Cockpit.sso.targetPath}")
    private String h5CockpitSSOTargetPath;
    @Value("${h5Cockpit.sso.testUserInfo}")
    private String h5CockpitSSOTestUserInfo;


    @Override
    public Response userLogout() {
        return userService.logout();
    }

    @Override
    public Response getAuthCode(LoginQuery query) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        return loginService.getAuthCode(query, request);
    }

    @Override
    public Response userLogin(LoginQuery query) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        HttpServletResponse response = requestAttributes.getResponse();
        String account = query.getAccount();
        String encryptPassword = query.getPassword();
        if (StringUtils.isBlank(account)) {
            return Response.error(MSG.req_error_param_account_null);
        }
        if (StringUtils.isBlank(encryptPassword)) {
            return Response.error(MSG.req_error_param_password_null);
        }
        try {
            //私钥解密，如果被篡改，则会解密失败
            String password = TokenUtil.decrypt(encryptPassword, TokenUtil.PWD_PRIVATEKEY);

            UserDO queryDO = new UserDO();
            // 能同时使用账号或手机号登录
            queryDO.setAccount(account);
            UserDO userIndb = userService.selectOne(queryDO);
            if (userIndb == null) {
                queryDO.setAccount(null);
                queryDO.setPhone(account);
                userIndb = userService.selectOne(queryDO);
            }
            if (userIndb == null) {
                //账户不存在，但是提示模糊一点
                return Response.error(MSG.req_error_account_or_password_failed);
            }
            int authCodeIsOpen = 0;
            Response<SystemConfigDO> authCodeOpen = systemConfigService.getConfig(SystemConfigConstant.VERIFICATION_CODE);
            if (authCodeOpen.getData() != null) {
                authCodeIsOpen = Integer.parseInt(authCodeOpen.getData().getConfigValue());
            }
            if (authCodeIsOpen == 1) {
                // 每天密码错误限制
                Response<SystemConfigDO> result = systemConfigService.getConfig(SystemConfigConstant.PASSWORD_ERROR_COUNT_LIMIT);
                // 需要图形验证码次数
                Response<SystemConfigDO> passwordErrorCountResult = systemConfigService.getConfig(SystemConfigConstant.PASSWORD_ERROR_COUNT);
                SystemConfigDO systemConfigDO = result.getData();
                String count = "10";
                String passwordErrorCount = "10";
                if (systemConfigDO != null) {
                    count = systemConfigDO.getConfigValue();
                }
                if (passwordErrorCountResult.getData() != null) {
                    passwordErrorCount = passwordErrorCountResult.getData().getConfigValue();
                }
                // 密码错误更新密码尝试次数和密码尝试时间
                if (!checkTryTime(userIndb, count)) {
                    return Response.error(MSG.req_error_wrong_pwd_limit);
                }
                // 校验图形验证码
                String authCode = query.getAuthCode();
                if (StringUtils.isNotEmpty(authCode) && !MD5Util.MD5(authCode.toUpperCase()).equalsIgnoreCase(query.getAuthCodeEnc())) {
                    return Response.error(MSG.req_error_auth_code_error);
                }
                // 校验密码
                if (!AccountUtil.checkPwd(userIndb, password)) {
                    // 密码错误更新密码尝试次数和密码尝试时间
                    if (StringUtils.isEmpty(authCode) && !checkTryTime(userIndb, passwordErrorCount)) {
                        // 密码达到指定错误次数，返回显示验证码标识和验证码图片base64码
                        Map<String, Object> returnMap = new HashMap<>();
                        returnMap.put("needAuthCode", true);
                        userService.createAuthCode(response, returnMap);
                        return Response.success(returnMap);
                    }
                    // 更新密码尝试次数和尝试时间
                    userIndb.setTokenMD5("");
                    userDOMapper.updateByPrimaryKeySelective(userIndb);
                    return Response.error(MSG.req_error_account_or_password_failed);
                } else {
                    userIndb.setTryTime(0);
                    userIndb.setTryDate(new Date());
                    userDOMapper.updateByPrimaryKeySelective(userIndb);
                }
            }
            Long expiration = userIndb.getExpiration().getTime();
            if (System.currentTimeMillis() > expiration) {
                return Response.error(MSG.req_error_account_expire);
            }
            if (userIndb.getStatus() != null && userIndb.getStatus() != 0) {
                return Response.error(MSG.req_error_account_status_failed);
            }

            //判断盐值是否赠缺
            String salt = CookieUtils.getLoginSalt(request);
            if (StringUtils.isBlank(salt)) {
                log.warn("盐值不存在，认证失败");
                return Response.error(MSG.req_login_authentication_fail);
            }
            String md5Sign = AccountUtil.getSalt(account, encryptPassword, request.getHeader("Origin"));
            if (!salt.equals(md5Sign)) {
                log.warn("盐值校验失败，盐值不一样");
                return Response.error(MSG.req_login_authentication_fail);
            }
            Example selectExample = new Example(SaltDO.class);
            selectExample.createCriteria().andEqualTo("salt", md5Sign);
            List<SaltDO> saltDOList = saltDOMapper.selectByExample(selectExample);
            if (CollectionUtils.isEmpty(saltDOList)) {
                log.warn("查询db中盐值不存在，认证失败");
                return Response.error(MSG.req_login_authentication_fail);
            }

            Response<Map> loginResponse = loginService.userLogin(response, userIndb, RequestUtil.getIpAddr(request));
            if (!loginResponse.isSuccess()) {
                return loginResponse;
            } else {
                //登录成功之后删除当前盐值
                Example deleletExample = new Example(SaltDO.class);
                deleletExample.createCriteria().andEqualTo("salt", md5Sign);
                saltDOMapper.deleteByExample(deleletExample);
                //删除过期盐值
                Example deleteExample = new Example(SaltDO.class);
                deleteExample.createCriteria().andLessThan("expiration", new Date());
                saltDOMapper.deleteByExample(deleteExample);
            }

            return loginResponse;
        } catch (Exception e) {
            log.error("登录失败", e);
        }
        return Response.error(MSG.req_error_login_failed);
    }

    @Override
    public Response todoSSOLogin(@RequestParam(name = "ticket", required = false) String ticket,
                                 @RequestParam(name = "redirectUrl", required = false) String redirectUrl) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        HttpServletResponse response = requestAttributes.getResponse();
        if (StringUtils.isBlank(ticket)) {
            log.error("[todoSSOLogin]: ticket is blank ");
            return Response.error(MSG.old_param_error);
        }
        if (StringUtils.isBlank(redirectUrl)) {
            log.warn("[todoSSOLogin]: ticket is blank ");
            redirectUrl = "/"; // 重定向允许为空,让前端跳首页即可
        }
        String account = oaTicketAccountMapper.selectByTicket(ticket);
        if (StringUtils.isBlank(account)) {
            log.error("根据待办ticket找不到对应account, ticket={}", ticket);
            return Response.error(MSG.req_error_account_not_exist);
        }
        log.info("[todoSSOLogin]:ticket={}, account={}", ticket, account);
        UserDO userDO = userService.queryByAccount(account);
        if (userDO == null) {
            log.error("根据account找不到对应userDO, account={}", account);
            return Response.error(MSG.req_error_account_not_exist);
        }

        // 执行内部登录，并加入重定向地址
        Response<Map> loginResponse = loginService.userLogin(response, userDO, RequestUtil.getIpAddr(request));
        if (loginResponse.isSuccess()) {
            // 修改下 重定向的路径
            log.info("[todoSSOLogin]:user login success, userDO={}", JSON.toJSONString(userDO));
            loginResponse.getData().put("indexPath", redirectUrl);
        }
        return loginResponse;
    }

    /**
     * 交投门户系统 到 智慧风控 免密登录
     * ticket 是 门户系统 跳转到 前端 /user/ssoLogin地址会带上的
     * service /user/ssoLogin 的完整 url encoding
     */
    @Override
    public Response portalSsoLogin(@RequestParam(name = "ticket", required = false) String ticket,
                                   @RequestParam(name = "service", required = false) String service) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        HttpServletResponse response = requestAttributes.getResponse();
        log.info("[portalSsoLogin]: portal/ssoLogin,ticket={}, service={}", ticket, service);
        log.info("profilesActive={}, cicoPortalIsTest={}", profilesActive, cicoPortalIsTest);
        if (StringUtils.isBlank(ticket)) {
            log.error("[portalSsoLogin]: ticket is blank ");
            return Response.error(MSG.old_param_error);
        }
        if (StringUtils.isBlank(service)) {
            log.warn("[portalSsoLogin]: service is blank ");
            service = cicoPortalService;
            log.info("[portalSsoLogin]: service default value={}", service);
        }
        try {
            String account;
            UserDO userDO;
            if (cicoPortalIsTest) {
                log.info("cicoPortalIsTest = true, 直接mock admin账户，跳过cico门户接口请求");
                // 测试登录admin账号
                account = "admin";
                userDO = userService.queryByAccount(account);
                if (ObjectUtils.isEmpty(userDO)) {
                    return Response.error(MSG.req_error_account_not_exist);
                }
            } else {
                // 通过 ticket 和 service 获取 用户信息
                MultiValueMap<String, Object> param = new LinkedMultiValueMap<>();
                param.put("ticket", Collections.singletonList(ticket));
                param.put("service", Collections.singletonList(service));
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
                HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(param, headers);

                log.info("[portalSsoLogin]: restTemplate invoke start, url={}", cicoPortalValidateUrl);
                String result = restTemplate.postForObject(cicoPortalValidateUrl, httpEntity, String.class);
                log.info("[portalSsoLogin]: ticket={} --- service={} --- result={}", ticket, service, result);
                // doom4j 解析 result
                Document doc = DocumentHelper.parseText(result);
                Element root = doc.getRootElement();
                Element authenticationFailure = root.element("authenticationFailure");
                // 校验ticket失败逻辑
                if (!ObjectUtils.isEmpty(authenticationFailure)) {
                    log.error("[portalSsoLogin]: ticket={} --- service={} --- authenticationFailure={}", ticket, service, authenticationFailure.getTextTrim());
                    return Response.error(MSG.req_error_account_not_exist);
                }
                // 校验ticket成功逻辑
                Element authenticationSuccess = root.element("authenticationSuccess");
                account = null;
                if (!ObjectUtils.isEmpty(authenticationSuccess)) {
                    Element attributes = authenticationSuccess.element("attributes");
                    if (!ObjectUtils.isEmpty(attributes)) {
                        Element workcode = attributes.element("workcode");
                        account = workcode.getTextTrim();
                    }
                }
                // 用户登录
                if (StringUtil.isBlank(account)) {
                    log.info("[portalSsoLogin]:workcode={}", account);
                    //账户不存在，但是提示模糊一点
                    return Response.error(MSG.req_error_account_not_exist);
                }
                log.info("[portalSsoLogin]:workcode={}", account);
                userDO = userService.queryByAccount(account);
            }
            if (ObjectUtils.isEmpty(userDO)) {
                //账户不存在，但是提示模糊一点
                log.info("[portalSsoLogin]:user does not exist, workcode={}", account);
                return Response.error(MSG.req_error_account_not_exist);
            }
            Response<Map> loginResponse = loginService.userLogin(response, userDO, RequestUtil.getIpAddr(request));
            if (loginResponse.isSuccess()) {
                // 默认跳转地址为/，根据权限让前端来显示
                String redirectUrl = "/";
                // 驾驶舱页面权限校验，如果有，优先跳驾驶舱； 如果没有权限，还是默认跳首页
                final boolean hasPathPermission = loginService.checkUserMenu(userDO.getId(), cicoPortalTargetPath);
                log.info("用户{}权限校验完成，是否拥有/cockpit权限:{}", userDO.getAccount(), hasPathPermission);

                if (hasPathPermission) {
                    redirectUrl = cicoPortalTargetPath;
                }

                // set http cookie, 使用utf-8编码
                // 修改下 重定向的路径
                log.info("[portalSsoLogin]:user login success, userDO={}", JSON.toJSONString(userDO));
                loginResponse.getData().put("indexPath", redirectUrl);
            }
            return loginResponse;
        } catch (Exception e) {
            log.error("[portalSsoLogin]: ticket={} --- service={} --- exception:", ticket, service, e);
            return Response.error(MSG.req_error_account_not_exist);
        }
    }

    /**
     * 经分大屏 到 智慧风控 免密登录
     * ticket和orgToken 是 经分大屏 跳转到 前端 /user/dashboardSSOLogin地址会带上的
     * 访问集团接口用ticket和orgToken换用户信息，经过权限校验后，进行内部登录，并引导前端跳转至看板页面
     */
    @Override
    public Response<Map> dashboardSSOLogin(@RequestParam(name = "ticket", required = false) String ticket) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        HttpServletResponse response = requestAttributes.getResponse();
        log.info("dashboard/ssoLogin,ticket={}", ticket);
        if (StringUtils.isBlank(ticket)) {
            log.info(" ticket is null ");
            return Response.error(MSG.permission_menu_error);
        }

        try {
            //1. 校验ticket的合法性
            Map<String, Object> params = new HashMap<>();
            params.put("ticket", ticket);
            params.put("service", dashboardSSOService); // service是当前系统注册在sso中的地址，验证ticket有效性并换取用户信息时需要用到； 风控注册地址为http://zhfk.zjzsfh.com/sso, 融租易地址为http://10.158.19.43
            UserDO ssoLoginUserDO;
            String ssoIsTest = org.apache.commons.lang3.ObjectUtils.defaultIfNull(systemConfigService.getStringConfigValue("dashboardSSOIsTest"), "false");
            if (Boolean.parseBoolean(ssoIsTest)) {
                String dashboardSSOMockUserDO = systemConfigService.getStringConfigValue("dashboardSSOMockUserDO");
                if (StringUtils.isNotBlank(dashboardSSOMockUserDO)) {
                    ssoLoginUserDO = JSON.parseObject(dashboardSSOMockUserDO, UserDO.class);
                } else {
                    log.warn("驾驶舱免登入口mock开关打开但未定义mock用户json");
                    return Response.error(MSG.permission_menu_error);
                }
            } else {
                String xml = HttpClientUtil.invokePost(dashboardSSOValidateTicketUrl, params, null);
                log.info("驾驶舱跳转至风控系统单点登录, ticket={},, service={},泛微返回报文是{}", ticket, dashboardSSOService, xml);
                ssoLoginUserDO = this.parseWeaverXml(xml);
            }
            if (ssoLoginUserDO.getAccount() == null) {
                log.warn("未能从泛微sso返回结果获取用户工号，登录结束");
                return Response.error(MSG.permission_menu_error);
            }

            //2. 用户登录
            UserDO queryDO = new UserDO();
            queryDO.setAccount(ssoLoginUserDO.getAccount());
            UserDO userIndb = userService.selectOne(queryDO);
            if (userIndb == null) {
                log.warn("sso登录后用户在风控系统内不存在，account={}, username={}", ssoLoginUserDO.getAccount(), ssoLoginUserDO.getUserName());
                //账户不存在，但是提示模糊一点
                return Response.error(MSG.permission_menu_error);
            }

            String dashboardSSOTargetPath = systemConfigService.getStringConfigValue("dashboardSSOTargetPath");
            String targetUri = dashboardSSOTargetPath.contains("?") ? dashboardSSOTargetPath.substring(0, dashboardSSOTargetPath.indexOf("?")) : dashboardSSOTargetPath; // 只判断前面的/cockpit， 不判断后面的url参数

            // 权限校验，如果没有目标跳转地址的权限，不予登录，前端跳转到登录页面
            final boolean hasPathPermission = loginService.checkUserMenu(userIndb.getId(), targetUri);
            if (!hasPathPermission) {
                log.warn("sso登录用户");
                // 登录用户没有将要跳转的看板权限，不执行登录，直接返回错误，前端跳转登录页
                return Response.error(MSG.permission_menu_error);
            }

            Response<Map> loginResponse = loginService.userLogin(response, userIndb, RequestUtil.getIpAddr(request));
            if (!loginResponse.isSuccess()) {
                log.warn("用户内部登录失败，response={}", JSON.toJSONString(loginResponse));
                return loginResponse;
            } else {
                // set http cookie, 使用utf-8编码
                // 修改下 重定向的路径
                loginResponse.getData().put("indexPath", dashboardSSOTargetPath); // 目前从经分看板过来的，固定重定向到指标管理的统计看板页面
                if (StringUtils.isNotBlank(ssoLoginUserDO.getPhone())) {
                    loginResponse.getData().put("suffixPhone", ssoLoginUserDO.getPhone());
                }
                //暂时存到cookie，后面访问其他看板的时候需要用到
                CookieUtils.setCookieValByName(response, "_suffix_phone_", ssoLoginUserDO.getPhone());
                return loginResponse;
            }
        } catch (Exception e) {
            log.error("登录失败", e);
        }

        return Response.error(MSG.permission_menu_error);
    }

    /**
     * 集团 到 驾驶舱h5手机端页面 免密登录
     * ticket和orgToken 是 集团 跳转到 前端 /user/h5CockpitSSOLogin地址会带上的
     * 访问集团接口用ticket和orgToken换用户信息，经过权限校验后，进行内部登录，校验权限后引导前端跳转至驾驶舱页面
     * 兼容集团APP端两种参数名形式，多次发生变更
     */
    @Override
    public Response<Map> h5SSOLogin(@RequestParam(name = "loginId", required = false) String loginId) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        HttpServletResponse response = requestAttributes.getResponse();
        log.info("h5Cockpit/ssoLogin,loginId={}", loginId);
        if (StringUtils.isBlank(loginId)) {
            log.info(" loginId is null ");
            return Response.error(MSG.permission_menu_error);
        }

        try {
            //1. 解密loginid，拿到用户code(即系统内用户登录的account)
            String decryptedLoginId;
            if (h5CockpitSSOIsTest) {
                decryptedLoginId = h5CockpitSSOTestUserInfo;
                log.info("test on , decryptedLoginId={}", decryptedLoginId);
            } else {
                decryptedLoginId = DESUtil.decryptString(loginId, h5CockpitSSODesSecret);
                log.info("test off , decryptedLoginId={}", decryptedLoginId);
            }
            if (decryptedLoginId == null) {
                log.info("h5Cockpit免登，LoginId解密后为null，loginId={}", loginId);
                return Response.error(MSG.permission_menu_error);
            }
            decryptedLoginId = decryptedLoginId.toLowerCase(); // 因为APP端有将入参loginid改为loginId的前科，因此将body中的字符串都解析成小写后再解析json
            JSONObject loginJson = JSONObject.parseObject(decryptedLoginId);
            if (loginJson == null ||
                    !loginJson.containsKey("loginid") || loginJson.getString("loginid") == null) {
                log.error("免登对接结果解析异常,decryptedLoginId={}", decryptedLoginId);
                return Response.error(MSG.permission_menu_error);
            }
            String mobile = loginJson.getString("loginid");
            String account = cockpitMobileService.mappingMobileToAccount(mobile);
            if (StringUtils.isBlank(account)) {
                log.info("h5Cockpit免登，根据手机号找不到账号，mobile={}", mobile);
                return Response.error(MSG.permission_menu_error);
            }

            //2. 用户登录
            UserDO queryDO = new UserDO();
            queryDO.setAccount(account);
            UserDO userIndb = userService.selectOne(queryDO);
            if (userIndb == null) {
                log.error("用户{}不存在", account);
                //账户不存在，但是提示模糊一点
                return Response.error(MSG.permission_menu_error);
            }
            log.info("用户{}已找到", account);

            // 权限校验，如果没有目标跳转地址的权限，不予登录，前端跳转到登录页面
            final boolean hasPathPermission = loginService.checkUserMenu(userIndb.getId(), h5CockpitSSOTargetPath);
            log.info("用户权限校验：{}", hasPathPermission);
            if (!hasPathPermission) {
                // 登录用户没有将要跳转的看板权限，不执行登录，直接返回错误，前端跳转登录页
                return Response.error(MSG.permission_menu_error);
            }

            return loginService.userLogin(response, userIndb, RequestUtil.getIpAddr(request));
        } catch (Exception e) {
            log.error("交投 -> 驾驶舱h5 免登失败，loginId密文={}", loginId, e);
        }

        return Response.error(MSG.permission_menu_error);
    }

    @Override
    public Response<DisplayMenuTreeDO> menuTree() {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        // todo 暂时写死，暂时先不选择系统
        String systemCode = SystemConstant.APP_PASJ;
        if (SystemConstant.APP_ZHFK.equals(systemCode)) {
            String wlToken = CookieUtils.getWlToken(request);

            log.info("API /user/menuTree wlToken:" + wlToken);
            if (StringUtils.isBlank(wlToken)) {
                return Response.error(MSG.req_error_token_null);
            }
            CheckResult checkResult = JwtUtils.validateJWT(wlToken);

            if (checkResult.isSuccess()) {
                // 账号
                //String account = (String) checkResult.getClaims().get("sub");
                String account = request.getHeader("test-account");
                UserDO queryDO = new UserDO();
                queryDO.setAccount(account);
                UserDO loginUser = userService.selectOne(queryDO);
                if (loginUser == null) {
                    return Response.error(MSG.req_error_account_or_password_failed);
                }
                //true 要修改密码， false 不需要修改密码
                if (loginUser.getUpdatePwdTime() != null) {
                    loginUser.setUpdatePwdStatus(loginUser.getUpdatePwdTime().before(new Date()));
                }
                return loginService.getMenuTrees(loginUser);
            } else {
                return Response.error(1, "用户权限查询失败");
            }
        } else if (SystemConstant.APP_PASJ.equals(systemCode)) {
            return thirdApplicationAuthService.menuTree();
        } else {
            log.error("用户权限查询失败，未知的系统");
            return Response.error(1, "用户权限查询失败，未知的系统");
        }
    }

    /**
     * 对外提供用户检查服务
     *
     * @param tokenId
     * @param servletPath
     * @param method
     * @param paramJson   参数
     * @return
     */
    @Override
    public Response<UserDO> checkUser(String tokenId, String servletPath, String functionCode, String method, String paramJson) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        HttpServletResponse response = requestAttributes.getResponse();
        if (StringUtils.isEmpty(tokenId)) {
            log.info("wlToken验证失败");
            return Response.error(MSG.req_error_sign_not_exists);
        }
        CheckResult checkResult = JwtUtils.validateJWT(tokenId);
        UserDO loginUser = null;
        if (checkResult.isSuccess()) {
            String account = (String) checkResult.getClaims().get("sub");//账号

            if (StringUtils.isBlank(account)) {
                return Response.error(MSG.req_error_param_account_null);
            }

            UserDO queryDO = new UserDO();
            queryDO.setAccount(account);
            loginUser = userService.selectOne(queryDO);
            if (loginUser == null) {
                return Response.error(MSG.req_error_account_not_exist);
            }
            loginUser.setPwd(null);
            loginUser.setSalt(null);
            if (StringUtils.isBlank(servletPath) || StringUtils.isBlank(method)) {
                return Response.error(MSG.req_error_path_or_method_null);
            }
            if (!commonFunctionCheck.check(method, servletPath)) {
                // 非公共方法
                if (!loginService.checkUserMenuOrFunction(account, servletPath, method, functionCode, CookieUtils.getMenuId(request))) {
                    log.info("账号：" + account + "没有菜单功能权限");
                    return Response.error(MSG.req_error_account_no_auth);
                }
            }

//            OperationParamTO paramTO = new OperationParamTO();
//            Map<String, String[]> parameterMap = request.getParameterMap();
//            paramTO.setDetail(JSON.toJSONString(parameterMap));
            //TODO 记录操作日志
//            loginService.saveUserOperation(servletPath, method, account, OperationLogUtil.genOptLogParams(request, params));
        } else {
            switch (checkResult.getErrCode()) {
                // 签名验证不通过
                case SystemConstant.JWT_ERRCODE_FAIL:
                    log.info("签名验证不通过");
                    return Response.error(MSG.req_error_sign_not_pass);
                // 签名过期，返回过期提示码
                case SystemConstant.JWT_ERRCODE_EXPIRE:
                    log.info("签名过期");
                    return Response.error(MSG.req_error_sign_expire);
                default:
                    break;
            }
        }
        return Response.success(loginUser);
    }

    /**
     * 对外提供用户检查服务
     *
     * @return
     */
    @Override
    public Response<Map<String, Object>> checkToken() {
        return userService.checkToken();
    }

    /**
     * 查询图形验证码
     */
    @Override
    public Response doAuthCode() {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        HttpServletResponse response = requestAttributes.getResponse();
        Map<String, Object> map = new HashMap<>();
        userService.createAuthCode(response, map);
        return Response.success(map);
    }

    /**
     * 检查密码错误次数，一天最多错误10次
     *
     * @param userIndb
     * @return
     */
    private boolean checkTryTime(UserDO userIndb, String count) {
        Date now = new Date();
        // 判断是否为同一天
        if (userIndb.getTryDate() == null) {
            userIndb.setTryTime(1);
            userIndb.setTryDate(now);
            return true;
        }
        Calendar ca = Calendar.getInstance();
        ca.setTime(new Date());
        ca.set(Calendar.HOUR, 0);
        ca.set(Calendar.MINUTE, 0);
        ca.set(Calendar.SECOND, 0);
        if (userIndb.getTryDate().getTime() < ca.getTimeInMillis()) {
            userIndb.setTryTime(1);
            userIndb.setTryDate(now);
            return true;
        }
        if (count != null) {
            if (userIndb.getTryTime() < Integer.parseInt(count)) {
                userIndb.setTryTime(userIndb.getTryTime() + 1);
                return true;
            }
        }
        return false;
    }

    private UserDO parseWeaverXml(String xml) {
        UserDO userDO = new UserDO();
        Document document;
        try {
            document = DocumentHelper.parseText(xml);
        } catch (DocumentException e) {
            log.error("sso xml报文解析失败", e);
            return userDO;
        }
        Element root = document.getRootElement();
        Iterator a = root.elementIterator();
        if (a.hasNext()) {
            Element success = (Element) a.next();
            Iterator attributes = success.elementIterator("attributes");
            while (attributes.hasNext()) {
                Element attribute = (Element) attributes.next();
//                weaverUser.setUserId(Integer.parseInt(attribute.elementText("id")));
                userDO.setAccount(attribute.elementText("workcode"));
                userDO.setUserName(attribute.elementText("lastname"));
                userDO.setPhone(attribute.elementText("mobile"));
            }
        }
        return userDO;
    }
}
